﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EntityFrameworkContrib.PostSharp4EF;
using System.Collections;
using System.Runtime.Serialization;
using CyclicReferences.Public;

namespace EFContrib.PS4EF.Tst.RelationShipType
{
    /// <summary>
    /// Summary description for SerializeTest
    /// </summary>
    [TestClass]
    public class SerializeTest
    {
        #region setup
        public SerializeTest()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion
        #endregion

        [TestMethod]
        public void tstBasicSerialize()
        {
            Customer c = new Customer();

            Car car = new Car { Make = "Porsche", CarID = 1, Customer = c };

            ((ISerializationHelp)car).SetValueAsOriginal("Make", "OriginalMake");

            Type surrogateType = Type.GetType("EFContrib.PS4EF.Tst.RelationShipType.Car+EFContrib.PS4EF.Tst.RelationShipType.CarSurrogate, EFContrib.PS4EF.Tst");
            object surrogate = Activator.CreateInstance(surrogateType, new object[] { car, new List<object>() });


            Car car2 = new Car();
            car2.GetType().GetMethod("CopyDataFromSurrogate").Invoke(car2, new object[] { surrogate });

        }

        void ClearDatabase()
        {
            using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
            {
                foreach (Customer old in context.Customer)
                {
                    context.DeleteObject(old);
                }
                context.SaveChanges();
            }
        }

        [TestMethod]
        public void tstPrepareForSerialization()
        {
            ClearDatabase();

            using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
            {
                Customer c = new Customer { Name = "Ruurd Boeke" };
                Car car1 = new Car { Make = "Porsche", Customer = c };
                Car car2 = new Car { Make = "Ferrari", Customer = c };
                Order order1 = new Order { Amount = 10, Customer = c };

                context.AddToCustomer(c);

                context.SaveChanges();

                // there are now 'original values' in the context that it is change tracking

                c.Name = "Changed";
                Car car3 = new Car { Make = "BMW", Customer = c };

                // now we need to create a version of the object that can be serialized well
                // this will call the context to get the 'original' values and save them to the loaded state (using IEditableBusinessObject)
                ((IContextAware)c).CreateSerializableState(context);


                ISerializationHelp sH = (ISerializationHelp)c;
                IStorageAccess sA = (IStorageAccess)c;

                // original collection had 2 cars
                Assert.IsTrue(((ICollection)sH.GetOriginalValue("Cars")).Count == 2);

                Assert.IsTrue(sH.GetOriginalValue("Name") == "Ruurd Boeke");
            }
        }

        [TestMethod]
        public void tstDeserializeWithoutRelations()
        {
            ClearDatabase();

            Customer c = new Customer { Name = "Ruurd Boeke" };

            #region create a customer object with original values and such
            using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
            {
                context.AddToCustomer(c);

                context.SaveChanges();

                // there are now 'original values' in the context that it is change tracking
                c.Name = "Changed";

                ((IContextAware)c).CreateSerializableState(context);
            } 
            #endregion

            // customer has original values (name)
            Type surrogateType = Type.GetType("EFContrib.PS4EF.Tst.RelationShipType.Customer+EFContrib.PS4EF.Tst.RelationShipType.CustomerSurrogate, EFContrib.PS4EF.Tst");
            object surrogate = Activator.CreateInstance(surrogateType, new object[] { c, new List<object>() });

            Customer c2 = new Customer();
            c2.GetType().GetMethod("CopyDataFromSurrogate").Invoke(c2, new object[] { surrogate });

            Assert.IsTrue(c.Name == c2.Name);
            Assert.IsTrue(c.CustomerID == c2.CustomerID);

            ISerializationHelp hC = (ISerializationHelp)c;
            ISerializationHelp hC2 = (ISerializationHelp)c2;

            Assert.IsTrue(hC.GetOriginalValue("Name") == hC2.GetOriginalValue("Name"));
        }

        [TestMethod]
        public void tstDeserializeWithRelations()
        {
            ClearDatabase();

            Customer c = new Customer { Name = "Ruurd Boeke" };
            Car car1 = new Car { Make = "Porsche", Customer = c };

            #region create a customer object with original values and such
            using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
            {
                context.AddToCustomer(c);

                context.SaveChanges();

                // there are now 'original values' in the context that it is change tracking
                c.Name = "Changed";
                c.Cars.First().Make = "ChangedPorsche";
                // even add a new car
                Car car2 = new Car { Make = "BMW", Customer = c };  // added to customer

                ((IContextAware)c).CreateSerializableState(context);
            }
            #endregion

            // customer has original values (name)
            Type surrogateType = Type.GetType("EFContrib.PS4EF.Tst.RelationShipType.Customer+EFContrib.PS4EF.Tst.RelationShipType.CustomerSurrogate, EFContrib.PS4EF.Tst");
            object surrogate = Activator.CreateInstance(surrogateType, new object[] { c, new List<object>() });

            Customer c2 = new Customer();
            c2.GetType().GetMethod("CopyDataFromSurrogate").Invoke(c2, new object[] { surrogate });

            // since we are not attached, the relations have not been fixed.
            Assert.IsTrue(c2.Cars.Count == 0);

            Assert.IsTrue(c2.Name == "Changed");

            Assert.IsTrue(((List<Car>)((ISerializationHelp)c2).GetOriginalValue("Cars")).Count == 1);   // there was one car originally attached

            // this is used during attachment and will bypass the relationmanager
            ((ISerializationHelp)c2).BeginDeserialization();
            List<Car> cars = (List<Car>) c2.Cars;
            ((ISerializationHelp)c2).EndDeserialization();

            Assert.IsTrue(cars.Count == 2); // ah, so it was deserialized correctly!
        }

        [TestMethod]
        public void tstAttachToContextWithoutRelations()
        {
            ClearDatabase();

            Customer c = new Customer { Name = "Ruurd Boeke" };

            #region create a customer object with original values and such
            using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
            {
                context.AddToCustomer(c);

                context.SaveChanges();

                // there are now 'original values' in the context that it is change tracking
                c.Name = "Changed";

                ((IContextAware)c).CreateSerializableState(context);
            }
            #endregion

            // customer has original values (name)
            Type surrogateType = Type.GetType("EFContrib.PS4EF.Tst.RelationShipType.Customer+EFContrib.PS4EF.Tst.RelationShipType.CustomerSurrogate, EFContrib.PS4EF.Tst");
            object surrogate = Activator.CreateInstance(surrogateType, new object[] { c, new List<object>() });

            Customer c2 = new Customer();
            c2.GetType().GetMethod("CopyDataFromSurrogate").Invoke(c2, new object[] { surrogate });


            // so, now we have a changed object

            using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
            {
                // attach. the lambda is possible because our setnames happen to correspond to our typenames.
                ((IContextAware)c2).AttachGraphToContext(context, source => source.GetType().Name);

                context.SaveChanges();
            }
        }

        [TestMethod]
        public void tstAttachToContextWithRelations()
        {
            ClearDatabase();

            string MsgOnWire;

            Customer c = new Customer { Name = "Ruurd Boeke" };
            Car car1 = new Car { Make = "Porsche", Customer = c };

            #region create a customer object with original values and such
            using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
            {
                context.AddToCustomer(c);

                context.SaveChanges();

                // there are now 'original values' in the context that it is change tracking
                c.Name = "Changed";
                c.Cars.First().Make = "ChangedPorsche";
                // even add a new car
                Car car2 = new Car { Make = "BMW", Customer = c };  // added to customer

                ((IContextAware)c).CreateSerializableState(context);
            }
            #endregion

            // serialize
            DataContractSerializer s = new DataContractSerializer(c.GetType(), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
            MsgOnWire = s.GetWellFormedToContract(c);

            // deserialize
            DataContractSerializer s2 = new DataContractSerializer(c.GetType(), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
            Customer c2 = (Customer) s2.UnwrapFromString(MsgOnWire);
            


            Assert.IsTrue(c2.Cars.Count == 0);  // not yet attached, the relationmanager kicks in and returns 0


            using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
            {
                // attach. the lambda is possible because our setnames happen to correspond to our typenames.
                ((IContextAware)c2).AttachGraphToContext(context, source => source.GetType().Name);

                Assert.IsTrue(c2.Cars.Count == 2);  // properly attached, the relationmanager will now return the two cars

                context.SaveChanges();
            }
        }
    }
}
